import Foundation

protocol MPConvertibleModelType {}

extension MPConvertibleModelType {
    static func mp_set(_ value: Any, _ ptr: UnsafeMutableRawPointer) {
        (value as? Self).flatMap { ptr.mp_bind(type: self).pointee = $0 }
    }
    
    static func mp_get(_ ptr: UnsafeMutableRawPointer) -> Any? {
        if ptr.mp_bind(type: Int.self).pointee == 0 {
            return nil
        }
        return ptr.mp_bind(type: self).pointee
    }
}

func mp_typeProxy(_ type: Any.Type) -> MPConvertibleModelType.Type {
    return mp_bitCast(type1: (type, 0), type2: MPConvertibleModelType.Type.self)
}

func mp_typeKey(_ type: Any.Type) -> UInt {
    return mp_bitCast(type1: type, type2: UInt.self)
}

func mp_bitCast <T1, T2>(type1: T1, type2: T2.Type) -> T2 {
    return unsafeBitCast(type1, to: type2)
}

func mp_model_convert(_ value : Any, info : MPConvertiblePropertyInfo) -> Any? {
    switch info.propertyType {
        case is MPConvertibleDictionary.Type:
            return mp_dict(value, type: info.propertyType, subType: info.subType)
            
        case is MPConvertibleArray.Type:
            return mp_array(value, type: info.propertyType, subType: info.subType)
            
        case is MPConvertibleSet.Type:
            return mp_set(value, type: info.propertyType, subType: info.subType)
            
        default:
            return mp_model_normal_value(value, type: info.propertyType)
    }
}

func mp_model_normal_value(_ value : Any, type : Any.Type?) -> Any? {
    guard let _type = type else { return nil }
    switch _type {
        case is MPConvertibleString.Type:
            return mp_string(value, type: _type)
            
        case is MPConvertibleNumber.Type:
            return mp_number(value, type: _type)
            
        case is MPConvertibleUrl.Type:
            return mp_url(value)
            
        case is MPConvertibleData.Type:
            return mp_data(value, type: _type)
            
        case is MPConvertibleDate.Type:
            return mp_date(value)
            
        case is MPConvertible.Type:
            if let dic = mp_valueToDic(value), let convert = _type as? MPConvertible.Type {
                return convert.mp_model(dic)
            }
            return nil
            
        case is any RawRepresentable.Type:
            return (_type as? any RawRepresentable.Type)?.mp_enum(value)
            
        default:
            break
    }
    return value
}

func mp_json_convert(_ value : Any?, info : MPConvertiblePropertyInfo) -> Any? {
    guard let _value = value else {
        return nil
    }
    switch info.propertyType {
        case is MPConvertible.Type:
            return (_value as? MPConvertible)?.mp_json()
            
        case is MPConvertibleDictionary.Type:
            return mp_json_get_dict(value, info.subType)
            
        case is MPConvertibleArray.Type:
            return mp_json_get_array(value, info.subType)
        
        case is any RawRepresentable.Type:
            return (value as? (any RawRepresentable))?.rawValue
        
        default:
            break
    }
    return _value
}

func mp_json_get_array(_ value : Any?, _ type : Any.Type?) -> [Any]? {
    guard let array = value as? [Any] else { return nil }
    if type is MPConvertible.Type {
        return array.compactMap {
            return ($0 as? MPConvertible)?.mp_json()
        }
    }
    if type is any RawRepresentable.Type {
        return array.compactMap {
            return ($0 as? (any RawRepresentable))?.rawValue
        }
    }
    if type is MPConvertibleDictionary.Type {
        let subType = (type as? MPConvertibleDictionary.Type)?.subType()
        return array.compactMap {
            return mp_json_get_dict($0, subType)
        }
    }
    if type is MPConvertibleArray.Type {
        let subType = (type as? MPConvertibleArray.Type)?.mp_subType()
        return array.compactMap {
            return mp_json_get_array($0, subType)
        }
    }
    if type is MPConvertibleSet.Type {
        
    }
    return array
}

func mp_json_get_dict(_ value : Any?,_ type : Any.Type?) -> [String:Any]? {
    guard let dict = mp_valueToDic(value) else { return nil }
    if type is MPConvertible.Type {
        return dict.compactMapValues {
            return ($0 as? MPConvertible)?.mp_json()
        }
    }
    if type is MPConvertibleArray.Type {
        let subType = (type as? MPConvertibleArray.Type)?.mp_subType()
        return dict.compactMapValues {
            return mp_json_get_array($0, subType)
        }
    }
    if type is any RawRepresentable.Type {
        return dict.compactMapValues {
            return ($0 as? (any RawRepresentable))?.rawValue
        }
    }
    return dict
}

func mp_valueToDic(_ value : Any?) -> [String : Any]? {
    if let dict = value as? [String : Any] {
        return dict
    }
    if let data = value as? Data, let dict = data.mp_toDict() {
        return dict
    }
    if let data = value as? String, let dict = data.mp_toDict() {
        return dict
    }
    return nil
}
